import { ImprovedNoise } from '/lib/jsm/math/ImprovedNoise.js';
import { OrbitControls } from '/lib/jsm/controls/OrbitControls.js';
import { FirstPersonShoot } from './FirstPersonShoot.js';
import { World, System, Component, TagComponent, Types } from "../lib/ecsy.module.js";
import { PlayerSystem } from "./PlayerSystem.js";
import * as Components from "./Components.js";

export class Input{
    static key = {W:87,S:83};
    static keyMap = new Map();
    static setKey(key,bPress){
        Input.keyMap[key] = bPress;
    }
    static getKeyDown(key){
        const val = Input.keyMap.get(key);
        if(val){
            return val;
        } else {
            return false;
        }
    }
    
    static getKey(){
        
    }
    
    static getKeyUp(key){
        const val = Input.keyMap.get(key);
        if(val){
            return !val;
        } else {
            return true;
        }
    }
}

export class Game {
    static scene;
    static renderer;
    static camera;
    static physicsWorld;
    //调用类的构造方法
    constructor() {
        this.terrain = new Terrain();
        this.clock = new THREE.Clock();
        this.useControl = true;
    }
    init(scene,camera,renderer,physicsWorld){
        Game.scene = scene;
        Game.renderer = renderer;
        Game.camera = camera;
        Game.physicsWorld = physicsWorld;
        
        this.scene=scene;
        this.camera=camera;
        this.renderer=renderer;
        this.initObject();
        this.shootControl = new FirstPersonShoot(this.camera,this.scene,this.renderer);
        
        if(this.useControl){
            this.controls = new OrbitControls( this.camera, renderer.domElement );
            this.controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
            this.controls.dampingFactor = 0.05;

            this.controls.screenSpacePanning = false;

            this.controls.minDistance = 100;
            this.controls.maxDistance = 900;

            this.controls.maxPolarAngle = Math.PI *2;
        }
        
        this.world = new World();
        this.world.registerComponent(Components.PlayerComponent)
        .registerComponent(Components.Object3D)
        .registerSystem(PlayerSystem);
        
        let cube = MeshTool.makeCube(new THREE.Vector3(1,1,3),new THREE.Vector3(0,0,0.5));
        this.scene.add(cube);
        
        let entity = this.world.createEntity();
        entity.addComponent(Components.PlayerComponent,{x:0,y:0});
        entity.addComponent(Components.Object3D,{object:cube});
        this.lastTime = performance.now();
    }
    
    initObject() {
        this.terrain.init(this.scene);
    }
    //定义一般的方法
    frame() {
        if(this.useControl)
        {
            this.controls.update();
        } else {
            this.shootControl.update();
        }
        
        let time = performance.now();
        let delta = time - this.lastTime;
        this.world.execute(delta, time);
        
        this.lastTime = time;
        
    }
}

export class MeshTool{
    static makeCube(lenVec,pos){
        let geometry = new THREE.BoxGeometry(lenVec.x,lenVec.y,lenVec.z);
 
        let material = new THREE.MeshLambertMaterial( { color: 0xffff00 } );
        this.cube = new THREE.Mesh(geometry, material);
        this.cube.position.set(pos.x,pos.y,pos.z);
        
        return this.cube;
    }
}

class Terrain{
    constructor(){
        
    }
    init(scene){
        this.scene=scene;
        
        const pxGeometry = new THREE.BoxGeometry( 100, 1,100);
        const material = new THREE.MeshLambertMaterial( {color: 0xffffff, side: THREE.DoubleSide} );
        let plane = new THREE.Mesh( pxGeometry, material );
        Game.physics.addMesh(plane,0);
        
        const group = new THREE.Group();
        group.add( plane );
        for(let i=0;i<10;i++){
            let meshCube = MeshTool.makeCube(new THREE.Vector3(1,1,1),new THREE.Vector3(i*10-50,100,0));
            group.add( meshCube );
            Game.physics.addMesh(meshCube,1);
        }
        this.scene.add(group);
        
        //group.rotateX( -Math.PI / 4 );
        //group.position.set(0, 0 ,0);
        
        const light = new THREE.DirectionalLight( 0xffffff);
        light.position.set( 0, 0, 1 ).normalize();
        this.scene.add( light );
        
        const ambLight = new THREE.AmbientLight( 0xffffff,0.5 ); // soft white light
        scene.add( ambLight );
        
        const axesHelper = new THREE.AxesHelper( 5 );
        axesHelper.position.y=-10;
        axesHelper.rotation.x=Math.PI/2;
        this.scene.add( axesHelper );
    }
    
    function createRigidBody( threeObject, physicsShape, mass, pos, quat ) {
        threeObject.position.copy( pos );
        threeObject.quaternion.copy( quat );

        const transform = new Ammo.btTransform();
        transform.setIdentity();
        transform.setOrigin( new Ammo.btVector3( pos.x, pos.y, pos.z ) );
        transform.setRotation( new Ammo.btQuaternion( quat.x, quat.y, quat.z, quat.w ) );
        const motionState = new Ammo.btDefaultMotionState( transform );

        const localInertia = new Ammo.btVector3( 0, 0, 0 );
        physicsShape.calculateLocalInertia( mass, localInertia );

        const rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia );
        const body = new Ammo.btRigidBody( rbInfo );

        threeObject.userData.physicsBody = body;

        scene.add( threeObject );

        if ( mass > 0 ) {

            rigidBodies.push( threeObject );

            // Disable deactivation
            body.setActivationState( 4 );

        }

        Game.physicsWorld.addRigidBody( body );

    }
    
    generateHeight( width, height ) {

        const data = [], perlin = new ImprovedNoise(),
            size = width * height, z = Math.random() * 100;

        let quality = 2;

        for ( let j = 0; j < 4; j ++ ) {

            if ( j == 0 ) for ( let i = 0; i < size; i ++ ) data[ i ] = 0;

            for ( let i = 0; i < size; i ++ ) {

                const x = i % width, y = ( i / width ) | 0;
                data[ i ] += perlin.noise( x / quality, y / quality, z ) * quality;

            }

            quality *= 4;

        }

        return data;

    }
}